//
//  URLSessionEx.swift
//  NetEaseMusic
//
//  Created by SAGESSE on 2020/2/21.
//  Copyright © 2020 SAGESSE. All rights reserved.
//

import UIKit


open class URLSessionEx: URLSession {

    open class Plugin: NSObject {
        
        open class Task: NSObject {
            
            var url: URL?
            var request: URLRequest
            var forwarding: URLSessionTask?

            init(_ request: URLRequest) {
                self.url = request.url
                self.request = request
            }
            
            
            func redirecting(to request: URLRequest) {
                urlProtocol.map {
                    $0.client?.urlProtocol($0, wasRedirectedTo: request, redirectResponse: .init())
                }
            }
            func redirecting(to task: URLSessionTask) {
                forwarding = task
                forwarding?.resume()
            }
            func caching(from response: CachedURLResponse) {
                urlProtocol.map {
                    $0.client?.urlProtocol($0, cachedResponseIsValid: response)
                }
            }
            
            func receive(_ data: Data) {
                urlProtocol.map {
                    $0.client?.urlProtocol($0, didLoad: data)
                }
            }
            func receive(_ response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy) {
                urlProtocol.map {
                    $0.client?.urlProtocol($0, didReceive: response, cacheStoragePolicy: policy)
                }
            }
            func receive(_ response: URLResponse, data: Data, cacheStoragePolicy policy: URLCache.StoragePolicy) {
                urlProtocol.map {
                    $0.client?.urlProtocol($0, didReceive: response, cacheStoragePolicy: policy)
                    $0.client?.urlProtocol($0, didLoad: data)
                }
            }
            
            func finish() {
                urlProtocol.map {
                    $0.client?.urlProtocolDidFinishLoading($0)
                }
            }
            func fail(_ error: Error) {
                urlProtocol.map {
                    $0.client?.urlProtocol($0, didFailWithError: error)
                }
            }
            func cancel() {
                forwarding?.cancel()
                forwarding = nil
                urlProtocol.map {
                    $0.client?.urlProtocol($0, didFailWithError: error(NSURLErrorCancelled, url: url))
                }
            }

            func receive(_ challenge: URLAuthenticationChallenge) {
                urlProtocol.map {
                    $0.client?.urlProtocol($0, didReceive: challenge)
                }
            }
            func cancel(_ challenge: URLAuthenticationChallenge) {
                urlProtocol.map {
                    $0.client?.urlProtocol($0, didCancel: challenge)
                }
            }
            
            fileprivate weak var urlProtocol: URLProtocol?
        }
        fileprivate class Request: URLProtocol {
            
            /// The current exec task.
            lazy var custom: Plugin.Task = .init(self.request)
            
            /// Determines whether the protocol subclass can handle the specified task.
            override class func canInit(with task: URLSessionTask) -> Bool {
                guard let request = task.currentRequest else {
                    return false
                }
                return Plugin.plugins.contains {
                    return $0.supported(request)
                }
            }
            /// Determines whether the protocol subclass can handle the specified request.
            override class func canInit(with request: URLRequest) -> Bool {
                return Plugin.plugins.contains {
                    return $0.supported(request)
                }
            }
            
            /// Returns a canonical version of the specified request.
            override class func canonicalRequest(for request: URLRequest) -> URLRequest {
                return request
            }
            
            /// A scheduler is crate from URLRequest.
            override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
                super.init(request: request, cachedResponse: cachedResponse, client: client)
                self.custom.urlProtocol = self
            }
            /// A scheduler is crate from URLSessionTask.
            convenience init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
                // When the task not any request this is a fatal error.
                guard let request = task.originalRequest ?? task.currentRequest else {
                    fatalError()
                }
                self.init(request: request, cachedResponse: cachedResponse, client: client)
            }
            
            // Start and record the available plug-ins.
            override func startLoading() {
                Plugin.plugins.forEach {
                    // Check the network plugin availability.
                    guard !canncelled, $0.supported(request) else {
                        return
                    }
                    using.append($0)
                    $0.start(custom, interrupt: &canncelled)
                }
            }
            // Stop the plug-in in using.
            override func stopLoading() {
                custom.urlProtocol = nil
                custom.cancel()
                canncelled = true
                using.forEach {
                    $0.stop(custom)
                }
                using.removeAll()
            }
            
            
            private var using: [Plugin] = []
            private var canncelled: Bool = false
        }

        open func supported(_ request: URLRequest) -> Bool {
            return false
        }
        
        open func start(_ task: Task, interrupt: inout Bool) {
        }
        open func stop(_ task: Task) {
        }
        
        public static func register(_ plugin: Plugin) {
            plugins.append(plugin)
        }
        public static func unregister(_ plugin: Plugin) {
            plugins.removeAll { $0 === plugin }
        }
        
        fileprivate static var plugins: [Plugin] = {
            // Load all protocol class.
            let cls: AnyClass = NSClassFromString("__NSCFURLSessionConfiguration") ?? URLSessionConfiguration.self
            class_getInstanceMethod(cls, #selector(getter: URLSessionConfiguration.protocolClasses)).map { m1 in
                class_getInstanceMethod(cls, #selector(URLSessionConfiguration.xc_protocolClasses)).map { m2 in
                    method_exchangeImplementations(m1, m2)
                }
            }
            return []
        }()
    }
    
    static func error(_ code: Int, url: URL? = nil, description: String? = nil) -> NSError {
        
        var userInfo = [String: Any]()
        
        userInfo[NSURLErrorKey] = url
        userInfo[NSLocalizedDescriptionKey] = description ?? HTTPURLResponse.localizedString(forStatusCode: code)

        return NSError(domain: URLSessionExErrorDomain, code: code, userInfo: userInfo)
    }
}

public  let URLSessionExErrorDomain = "URLSessionExErrorDomain"
    

// MARK: -


fileprivate extension URLSessionConfiguration {
    
    // Register methods with all URLSessionConfiguration instance.
    @objc func xc_protocolClasses() -> [AnyClass]? {
        return (xc_protocolClasses() ?? []) + [URLSessionEx.Plugin.Request.self]
    }
}
